Add support for pulling from remote archives
authorColin Walters <walters@verbum.org>
Tue, 1 Nov 2011 00:24:38 +0000 (20:24 -0400)
committerColin Walters <walters@verbum.org>
Tue, 1 Nov 2011 00:28:47 +0000 (20:28 -0400)
This necessitated a large set of changes.

We now support an "archive" mode for repositories.  In this mode,
files are stored "packed" rather than hard linked.  This allows one to
e.g. store an OSTree repository with root-owned files as non-root.  It
is also used as the basis for serving repositories via HTTP.

While doing this I realized that GVariant is endianness-dependent; I
decided to just store all data in big endian.

21 files changed:
Makefile-src.am
configure.ac
src/libostree/ostree-core.c
src/libostree/ostree-core.h
src/libostree/ostree-repo.c
src/libostree/ostree-repo.h
src/main.c
src/ostree-http-backend.c [new file with mode: 0644]
src/ot-builtin-fsck.c
src/ot-builtin-init.c
src/ot-builtin-pull.c [new file with mode: 0644]
src/ot-builtin-remote.c
src/ot-builtins.h
tests/.gitignore
tests/Makefile
tests/libtest.sh
tests/ostree-http-server.c [deleted file]
tests/run-apache.c [new file with mode: 0644]
tests/t0012-pull.sh [new file with mode: 0755]
tests/t0013-commit-archive.sh [new file with mode: 0755]
tests/tmpdir-lifecycle.c [new file with mode: 0644]

index 9990ecdc7b7d4addf8a5a485f93c3c56f38ebce3..ae28761e32498fb3e375cadc5b8af3340a280a65 100644 (file)
@@ -40,8 +40,8 @@ libostree_la_SOURCES = src/libostree/ostree.h \
        src/libostree/ostree-checkout.c \
        src/libostree/ostree-checkout.h \
        $(NULL)
-libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-libostree_la_LIBADD = libotutil.la $(GIO_UNIX_LIBS)
+libostree_la_CFLAGS = -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+libostree_la_LIBADD = libotutil.la $(OT_COREBIN_DEP_LIBS)
 
 bin_PROGRAMS += ostree
 
@@ -53,10 +53,18 @@ ostree_SOURCES = src/main.c \
        src/ot-builtin-init.c \
        src/ot-builtin-link-file.c \
        src/ot-builtin-log.c \
+       src/ot-builtin-pull.c \
        src/ot-builtin-run-triggers.c \
        src/ot-builtin-remote.c \
        src/ot-builtin-rev-parse.c \
        src/ot-builtin-show.c \
        $(NULL)
-ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(GIO_UNIX_CFLAGS)
-ostree_LDADD = libotutil.la libostree.la $(GIO_UNIX_LIBS)
+ostree_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+ostree_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS)
+
+bin_PROGRAMS += ostree-http-backend
+
+ostree_http_backend_SOURCES = src/ostree-http-backend.c
+ostree_http_backend_CFLAGS = -I$(srcdir)/src -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_COREBIN_DEP_CFLAGS)
+ostree_http_backend_LDADD = libotutil.la libostree.la $(OT_COREBIN_DEP_LIBS)
+
index be78b1fa331c10a4f19bead30cd327aac268465c..92c06d5ee48bdd168680ede98607930ea46910f8 100644 (file)
@@ -32,6 +32,7 @@ LT_INIT
 PKG_PROG_PKG_CONFIG
 
 PKG_CHECK_MODULES(GIO_UNIX, [gio-unix-2.0 >= 2.28])
+PKG_CHECK_MODULES(OT_COREBIN_DEP, [libsoup-gnome-2.4 >= 2.34.0 gio-unix-2.0 >= 2.28])
 
 AM_PATH_PYTHON
 
index d92681b8cb229852212aa1da1164c9a21d3a4a84..ce54fbc4cd3263212deaf4e7ff49613a8b483b43 100644 (file)
 #include <sys/types.h>
 #include <attr/xattr.h>
 
-static char *
-stat_to_string (struct stat *stbuf)
+gboolean
+ostree_validate_checksum_string (const char *sha256,
+                                 GError    **error)
+{
+  if (strlen (sha256) != 64)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid rev '%s'", sha256);
+      return FALSE;
+    }
+  return TRUE;
+}
+
+
+void
+ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode)
 {
-  return g_strdup_printf ("%u:%u:%u",
-                          (guint32)(stbuf->st_mode & ~S_IFMT),
-                          (guint32)stbuf->st_uid, 
-                          (guint32)stbuf->st_gid);
+  guint32 perms = (mode & ~S_IFMT);
+  g_checksum_update (checksum, (guint8*) &uid, 4);
+  g_checksum_update (checksum, (guint8*) &gid, 4);
+  g_checksum_update (checksum, (guint8*) &perms, 4);
 }
 
 static char *
@@ -144,6 +158,7 @@ ostree_get_xattrs_for_path (const char *path,
     }
 
   ret = g_variant_builder_end (&builder);
+  g_variant_ref_sink (ret);
  out:
   if (!ret)
     g_variant_builder_clear (&builder);
@@ -154,9 +169,10 @@ ostree_get_xattrs_for_path (const char *path,
 
 gboolean
 ostree_stat_and_checksum_file (int dir_fd, const char *path,
-                                 GChecksum **out_checksum,
-                                 struct stat *out_stbuf,
-                                 GError **error)
+                               OstreeObjectType objtype,
+                               GChecksum **out_checksum,
+                               struct stat *out_stbuf,
+                               GError **error)
 {
   GChecksum *content_sha256 = NULL;
   GChecksum *content_and_meta_sha256 = NULL;
@@ -202,10 +218,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
         }
     }
 
-  stat_string = stat_to_string (&stbuf);
-  xattrs = ostree_get_xattrs_for_path (path, error);
-  if (!xattrs)
-    goto out;
+  if (objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      xattrs = ostree_get_xattrs_for_path (path, error);
+      if (!xattrs)
+        goto out;
+    }
 
   content_sha256 = g_checksum_new (G_CHECKSUM_SHA256);
  
@@ -224,6 +242,8 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
   else if (S_ISLNK(stbuf.st_mode))
     {
       symlink_target = g_malloc (PATH_MAX);
+
+      g_assert (objtype == OSTREE_OBJECT_TYPE_FILE);
       
       bytes_read = readlinkat (dir_fd, basename, symlink_target, PATH_MAX);
       if (bytes_read < 0)
@@ -235,6 +255,7 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
     }
   else if (S_ISCHR(stbuf.st_mode) || S_ISBLK(stbuf.st_mode))
     {
+      g_assert (objtype == OSTREE_OBJECT_TYPE_FILE);
       device_id = g_strdup_printf ("%u", (guint)stbuf.st_rdev);
       g_checksum_update (content_sha256, (guint8*)device_id, strlen (device_id));
     }
@@ -249,8 +270,12 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
 
   content_and_meta_sha256 = g_checksum_copy (content_sha256);
 
-  g_checksum_update (content_and_meta_sha256, (guint8*)stat_string, strlen (stat_string));
-  g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+  if (objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      ostree_checksum_update_stat (content_and_meta_sha256, stbuf.st_uid,
+                                   stbuf.st_gid, stbuf.st_mode);
+      g_checksum_update (content_and_meta_sha256, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+    }
 
   *out_stbuf = stbuf;
   *out_checksum = content_and_meta_sha256;
@@ -267,6 +292,264 @@ ostree_stat_and_checksum_file (int dir_fd, const char *path,
     g_variant_unref (xattrs);
   if (content_sha256)
     g_checksum_free (content_sha256);
+  return ret;
+}
+
+gboolean
+ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error)
+{
+  gboolean ret = FALSE;
+  int i, n;
+
+  n = g_variant_n_children (xattrs);
+  for (i = 0; i < n; i++)
+    {
+      const guint8* name;
+      GVariant *value;
+      const guint8* value_data;
+      gsize value_len;
+      gboolean loop_err;
+
+      g_variant_get_child (xattrs, i, "(^&ay@ay)",
+                           &name, &value);
+      value_data = g_variant_get_fixed_array (value, &value_len, 1);
+      
+      loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, XATTR_REPLACE) < 0;
+      
+      g_variant_unref (value);
+      if (loop_err)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+gboolean
+ostree_parse_metadata_file (const char                  *path,
+                            OstreeSerializedVariantType *out_type,
+                            GVariant                   **out_variant,
+                            GError                     **error)
+{
+  GMappedFile *mfile = NULL;
+  gboolean ret = FALSE;
+  GVariant *ret_variant = NULL;
+  GVariant *container = NULL;
+  guint32 ret_type;
+
+  mfile = g_mapped_file_new (path, FALSE, error);
+  if (mfile == NULL)
+    {
+      goto out;
+    }
+  else
+    {
+      container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
+                                           g_mapped_file_get_contents (mfile),
+                                           g_mapped_file_get_length (mfile),
+                                           FALSE,
+                                           (GDestroyNotify) g_mapped_file_unref,
+                                           mfile);
+      g_variant_get (container, "(uv)",
+                     &ret_type, &ret_variant);
+      if (ret_type <= 0 || ret_type > OSTREE_SERIALIZED_VARIANT_LAST)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted metadata object '%s'; invalid type %d", path, ret_type);
+          goto out;
+        }
+      mfile = NULL;
+    }
 
+  ret = TRUE;
+  *out_type = ret_type;
+  *out_variant = ret_variant;
+  ret_variant = NULL;
+ out:
+  if (ret_variant)
+    g_variant_unref (ret_variant);
+  if (container != NULL)
+    g_variant_unref (container);
+  if (mfile != NULL)
+    g_mapped_file_unref (mfile);
   return ret;
 }
+
+char *
+ostree_get_relative_object_path (const char *checksum,
+                                 OstreeObjectType type,
+                                 gboolean         archive)
+{
+  GString *path;
+  const char *type_string;
+
+  g_assert (strlen (checksum) == 64);
+
+  path = g_string_new ("objects/");
+
+  g_string_append_len (path, checksum, 2);
+  g_string_append_c (path, '/');
+  g_string_append (path, checksum + 2);
+  switch (type)
+    {
+    case OSTREE_OBJECT_TYPE_FILE:
+      if (archive)
+        type_string = ".packfile";
+      else
+        type_string = ".file";
+      break;
+    case OSTREE_OBJECT_TYPE_META:
+      type_string = ".meta";
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+  g_string_append (path, type_string);
+  return g_string_free (path, FALSE);
+}
+
+gboolean
+ostree_pack_object (GOutputStream     *output,
+                    GFile             *file,
+                    OstreeObjectType  objtype,
+                    GCancellable     *cancellable,
+                    GError          **error)
+{
+  gboolean ret = FALSE;
+  char *path = NULL;
+  GFileInfo *finfo = NULL;
+  GFileInputStream *instream = NULL;
+  gboolean pack_builder_initialized = FALSE;
+  GVariantBuilder pack_builder;
+  GVariant *pack_variant = NULL;
+  GVariant *xattrs = NULL;
+  gsize bytes_written;
+
+  path = g_file_get_path (file);
+
+  finfo = g_file_query_info (file, "standard::type,standard::size,standard::is-symlink,standard::symlink-target,unix::*",
+                             G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, cancellable, error);
+  if (!finfo)
+    goto out;
+
+  if (objtype == OSTREE_OBJECT_TYPE_META)
+    {
+      guint64 object_size_be = GUINT64_TO_BE ((guint64)g_file_info_get_size (finfo));
+      if (!g_output_stream_write_all (output, &object_size_be, 8, &bytes_written, cancellable, error))
+        goto out;
+
+      instream = g_file_read (file, NULL, error);
+      if (!instream)
+        goto out;
+      
+      if (g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error) < 0)
+        goto out;
+    }
+  else
+    {
+      guint32 uid, gid, mode;
+      guint32 device = 0;
+      guint32 metadata_size_be;
+      const char *target = NULL;
+      guint64 object_size;
+
+      uid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_UID);
+      gid = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_GID);
+      mode = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+      g_variant_builder_init (&pack_builder, G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT));
+      pack_builder_initialized = TRUE;
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (0));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (uid));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (gid));
+      g_variant_builder_add (&pack_builder, "u", GUINT32_TO_BE (mode));
+
+      xattrs = ostree_get_xattrs_for_path (path, error);
+      if (!xattrs)
+        goto out;
+      g_variant_builder_add (&pack_builder, "@a(ayay)", xattrs);
+
+      if (S_ISREG (mode))
+        {
+          object_size = (guint64)g_file_info_get_size (finfo);
+        }
+      else if (S_ISLNK (mode))
+        {
+          target = g_file_info_get_attribute_byte_string (finfo, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET);
+          object_size = strlen (target);
+        }
+      else if (S_ISBLK (mode) || S_ISCHR (mode))
+        {
+          device = g_file_info_get_attribute_uint32 (finfo, G_FILE_ATTRIBUTE_UNIX_DEVICE);
+          object_size = 4;
+        }
+      else
+        g_assert_not_reached ();
+
+      g_variant_builder_add (&pack_builder, "t", GUINT64_TO_BE (object_size));
+      pack_variant = g_variant_builder_end (&pack_builder);
+      pack_builder_initialized = FALSE;
+
+      metadata_size_be = GUINT32_TO_BE (g_variant_get_size (pack_variant));
+
+      if (!g_output_stream_write_all (output, &metadata_size_be, 4,
+                                      &bytes_written, cancellable, error))
+        goto out;
+      g_assert (bytes_written == 4);
+
+      if (!g_output_stream_write_all (output, g_variant_get_data (pack_variant), g_variant_get_size (pack_variant),
+                                      &bytes_written, cancellable, error))
+        goto out;
+
+      if (S_ISREG (mode))
+        {
+          instream = g_file_read (file, NULL, error);
+          if (!instream)
+            goto out;
+          bytes_written = g_output_stream_splice (output, (GInputStream*)instream, 0, cancellable, error);
+          if (bytes_written < 0)
+            goto out;
+          if (bytes_written != object_size)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "File size changed unexpectedly");
+              goto out;
+            }
+        }
+      else if (S_ISLNK (mode))
+        {
+          if (!g_output_stream_write_all (output, target, object_size,
+                                          &bytes_written, cancellable, error))
+            goto out;
+        }
+      else if (S_ISBLK (mode) || S_ISCHR (mode))
+        {
+          guint32 device_be = GUINT32_TO_BE (device);
+          g_assert (object_size == 4);
+          if (!g_output_stream_write_all (output, &device_be, object_size,
+                                          &bytes_written, cancellable, error))
+            goto out;
+          g_assert (bytes_written == 4);
+        }
+      else
+        g_assert_not_reached ();
+    }
+  
+  ret = TRUE;
+ out:
+  g_free (path);
+  g_clear_object (&finfo);
+  g_clear_object (&instream);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  if (pack_builder_initialized)
+    g_variant_builder_clear (&pack_builder);
+  if (pack_variant)
+    g_variant_unref (pack_variant);
+  return ret;
+}
+
index 6d80941f01eb8d1ee42608d48697d2337c65af1f..3c1691895d849c462c82c72ca4f02e5643402574 100644 (file)
@@ -39,6 +39,7 @@ typedef enum {
   OSTREE_SERIALIZED_DIRMETA_VARIANT = 3,
   OSTREE_SERIALIZED_XATTR_VARIANT = 4
 } OstreeSerializedVariantType;
+#define OSTREE_SERIALIZED_VARIANT_LAST 4
 
 #define OSTREE_SERIALIZED_VARIANT_FORMAT "(uv)"
 
@@ -83,13 +84,54 @@ typedef enum {
  */
 #define OSTREE_COMMIT_GVARIANT_FORMAT "(ua{sv}ssstss)"
 
-GVariant *ostree_get_xattrs_for_path (const char *path,
-                                        GError    **error);
+gboolean ostree_validate_checksum_string (const char *sha256,
+                                          GError    **error);
+
+char *ostree_get_relative_object_path (const char *checksum,
+                                       OstreeObjectType type,
+                                       gboolean         archive);
+
+GVariant *ostree_get_xattrs_for_path (const char   *path,
+                                      GError     **error);
+
+gboolean ostree_set_xattrs (const char *path, GVariant *xattrs, GError **error);
+
+gboolean ostree_parse_metadata_file (const char                  *path,
+                                     OstreeSerializedVariantType *out_type,
+                                     GVariant                   **out_variant,
+                                     GError                     **error);
 
 gboolean ostree_stat_and_checksum_file (int dirfd, const char *path,
-                                          GChecksum **out_checksum,
-                                          struct stat *out_stbuf,
-                                          GError **error);
+                                        OstreeObjectType type,
+                                        GChecksum **out_checksum,
+                                        struct stat *out_stbuf,
+                                        GError **error);
+
+/* Packed files:
+ *
+ * guint32 metadata_length [metadata gvariant] [content]
+ *
+ * metadata variant:
+ * u - Version
+ * u - uid
+ * u - gid
+ * u - mode
+ * a(ayay) - xattrs
+ * t - content length
+ *
+ * And then following the end of the variant is the content.  If
+ * symlink, then this is the target; if device, then device ID as
+ * network byte order uint32.
+ */
+#define OSTREE_PACK_FILE_VARIANT_FORMAT "(uuuua(ayay)t)"
+
+gboolean  ostree_pack_object (GOutputStream     *output,
+                              GFile             *path,
+                              OstreeObjectType  objtype,
+                              GCancellable     *cancellable,
+                              GError          **error);
+
+void ostree_checksum_update_stat (GChecksum *checksum, guint32 uid, guint32 gid, guint32 mode);
 
 
 #endif /* _OSTREE_REPO */
index 964ce6950c7a68734b2b5985cf065b1c4228db8c..a1323c6966fca2b88f5728beacb4e6b0d51a28cb 100644 (file)
@@ -57,12 +57,14 @@ struct _OstreeRepoPrivate {
   char *path;
   GFile *repo_file;
   GFile *local_heads_dir;
+  GFile *remote_heads_dir;
   char *objects_path;
   char *config_path;
 
   gboolean inited;
 
   GKeyFile *config;
+  gboolean archive;
 };
 
 static void
@@ -74,6 +76,7 @@ ostree_repo_finalize (GObject *object)
   g_free (priv->path);
   g_clear_object (&priv->repo_file);
   g_clear_object (&priv->local_heads_dir);
+  g_clear_object (&priv->remote_heads_dir);
   g_free (priv->objects_path);
   g_free (priv->config_path);
   if (priv->config)
@@ -140,6 +143,7 @@ ostree_repo_constructor (GType                  gtype,
   
   priv->repo_file = ot_util_new_file_for_path (priv->path);
   priv->local_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/heads");
+  priv->remote_heads_dir = g_file_resolve_relative_path (priv->repo_file, "refs/remotes");
   
   priv->objects_path = g_build_filename (priv->path, "objects", NULL);
   priv->config_path = g_build_filename (priv->path, "config", NULL);
@@ -179,19 +183,6 @@ ostree_repo_new (const char *path)
   return g_object_new (OSTREE_TYPE_REPO, "path", path, NULL);
 }
 
-static gboolean
-validate_checksum_string (const char *sha256,
-                          GError    **error)
-{
-  if (strlen (sha256) != 64)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid rev '%s'", sha256);
-      return FALSE;
-    }
-  return TRUE;
-}
-
 static gboolean
 parse_rev_file (OstreeRepo     *self,
                 const char     *path,
@@ -252,7 +243,7 @@ parse_rev_file (OstreeRepo     *self,
     }
   else 
     {
-      if (!validate_checksum_string (rev, error))
+      if (!ostree_validate_checksum_string (rev, error))
         goto out;
     }
 
@@ -304,7 +295,7 @@ resolve_rev (OstreeRepo     *self,
        {
          g_strchomp (ret_rev);
          
-         if (!validate_checksum_string (ret_rev, error))
+         if (!ostree_validate_checksum_string (ret_rev, error))
            goto out;
        }
    }
@@ -357,12 +348,88 @@ write_checksum_file (GFile *parentdir,
   return ret;
 }
 
+/**
+ * ostree_repo_get_config:
+ * @self:
+ *
+ * Returns: (transfer none): The repository configuration; do not modify
+ */
+GKeyFile *
+ostree_repo_get_config (OstreeRepo *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, NULL);
+
+  return priv->config;
+}
+
+/**
+ * ostree_repo_copy_config:
+ * @self:
+ *
+ * Returns: (transfer full): A newly-allocated copy of the repository config
+ */
+GKeyFile *
+ostree_repo_copy_config (OstreeRepo *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  GKeyFile *copy;
+  char *data;
+  gsize len;
+
+  g_return_val_if_fail (priv->inited, NULL);
+
+  copy = g_key_file_new ();
+  data = g_key_file_to_data (priv->config, &len, NULL);
+  if (!g_key_file_load_from_data (copy, data, len, 0, NULL))
+    g_assert_not_reached ();
+  g_free (data);
+  return copy;
+}
+
+/**
+ * ostree_repo_write_config:
+ * @self:
+ * @new_config: Overwrite the config file with this data.  Do not change later!
+ * @error: a #GError
+ *
+ * Save @new_config in place of this repository's config file.  Note
+ * that @new_config should not be modified after - this function
+ * simply adds a reference.
+ */
+gboolean
+ostree_repo_write_config (OstreeRepo *self,
+                          GKeyFile   *new_config,
+                          GError    **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  char *data = NULL;
+  gsize len;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  data = g_key_file_to_data (new_config, &len, error);
+  if (!g_file_set_contents (priv->config_path, data, len, error))
+    goto out;
+  
+  g_key_file_unref (priv->config);
+  priv->config = g_key_file_ref (new_config);
+
+  ret = TRUE;
+ out:
+  g_free (data);
+  return ret;
+}
+
 gboolean
 ostree_repo_check (OstreeRepo *self, GError **error)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
   gboolean ret = FALSE;
   char *version = NULL;;
+  GError *temp_error = NULL;
 
   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
@@ -383,9 +450,12 @@ ostree_repo_check (OstreeRepo *self, GError **error)
       goto out;
     }
 
-  version = g_key_file_get_value (priv->config, "core", "repo_version", error);
-  if (!version)
-    goto out;
+  version = g_key_file_get_value (priv->config, "core", "repo_version", &temp_error);
+  if (temp_error)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
 
   if (strcmp (version, "0") != 0)
     {
@@ -394,6 +464,20 @@ ostree_repo_check (OstreeRepo *self, GError **error)
       goto out;
     }
 
+  priv->archive = g_key_file_get_boolean (priv->config, "core", "archive", &temp_error);
+  if (temp_error)
+    {
+      if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&temp_error);
+        }
+      else
+        {
+          g_propagate_error (error, temp_error);
+          goto out;
+        }
+    }
+
   priv->inited = TRUE;
   
   ret = TRUE;
@@ -402,6 +486,23 @@ ostree_repo_check (OstreeRepo *self, GError **error)
   return ret;
 }
 
+const char *
+ostree_repo_get_path (OstreeRepo  *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  return priv->path;
+}
+
+gboolean      
+ostree_repo_is_archive (OstreeRepo  *self)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+
+  g_return_val_if_fail (priv->inited, FALSE);
+
+  return priv->archive;
+}
+
 static gboolean
 import_gvariant_object (OstreeRepo  *self,
                         OstreeSerializedVariantType type,
@@ -463,54 +564,13 @@ load_gvariant_object_unknown (OstreeRepo  *self,
                               GVariant     **out_variant,
                               GError       **error)
 {
-  GMappedFile *mfile = NULL;
   gboolean ret = FALSE;
-  GVariant *ret_variant = NULL;
-  GVariant *container = NULL;
   char *path = NULL;
-  guint32 ret_type;
 
   path = get_object_path (self, sha256, OSTREE_OBJECT_TYPE_META);
-  
-  mfile = g_mapped_file_new (path, FALSE, error);
-  if (mfile == NULL)
-    goto out;
-  else
-    {
-      container = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT),
-                                           g_mapped_file_get_contents (mfile),
-                                           g_mapped_file_get_length (mfile),
-                                           FALSE,
-                                           (GDestroyNotify) g_mapped_file_unref,
-                                           mfile);
-      if (!g_variant_is_of_type (container, G_VARIANT_TYPE (OSTREE_SERIALIZED_VARIANT_FORMAT)))
-        {
-          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                       "Corrupted metadata object '%s'", sha256);
-          goto out;
-        }
-      g_variant_get (container, "(uv)",
-                     &ret_type, &ret_variant);
-      mfile = NULL;
-    }
-
-  ret = TRUE;
- out:
-  if (!ret)
-    {
-      if (ret_variant)
-        g_variant_unref (ret_variant);
-    }
-  else
-    {
-      *out_type = ret_type;
-      *out_variant = ret_variant;
-    }
-  if (container != NULL)
-    g_variant_unref (container);
+  ret = ostree_parse_metadata_file (path, out_type, out_variant, error);
   g_free (path);
-  if (mfile != NULL)
-    g_mapped_file_unref (mfile);
+
   return ret;
 }
 
@@ -534,7 +594,6 @@ load_gvariant_object (OstreeRepo  *self,
                    "Corrupted metadata object '%s'; found type %u, expected %u", sha256,
                    type, (guint32)expected_type);
       goto out;
-      
     }
 
   ret = TRUE;
@@ -616,41 +675,26 @@ get_object_path (OstreeRepo  *self,
                  OstreeObjectType type)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
-  char *checksum_prefix;
-  char *base_path;
   char *ret;
-  const char *type_string;
+  char *relpath;
 
-  checksum_prefix = g_strndup (checksum, 2);
-  base_path = g_build_filename (priv->objects_path, checksum_prefix, checksum + 2, NULL);
-  switch (type)
-    {
-    case OSTREE_OBJECT_TYPE_FILE:
-      type_string = ".file";
-      break;
-    case OSTREE_OBJECT_TYPE_META:
-      type_string = ".meta";
-      break;
-    default:
-      g_assert_not_reached ();
-    }
-  ret = g_strconcat (base_path, type_string, NULL);
-  g_free (base_path);
-  g_free (checksum_prefix);
+  relpath = ostree_get_relative_object_path (checksum, type, priv->archive);
+  ret = g_build_filename (priv->path, relpath, NULL);
+  g_free (relpath);
  
   return ret;
 }
 
 static char *
 prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
-                                          GChecksum    *checksum,
+                                          const char   *checksum,
                                           OstreeObjectType type,
                                           GError      **error)
 {
   char *checksum_dir = NULL;
   char *object_path = NULL;
 
-  object_path = get_object_path (self, g_checksum_get_string (checksum), type);
+  object_path = get_object_path (self, checksum, type);
   checksum_dir = g_path_get_dirname (object_path);
 
   if (!ot_util_ensure_directory (checksum_dir, FALSE, error))
@@ -662,21 +706,23 @@ prepare_dir_for_checksum_get_object_path (OstreeRepo *self,
 }
 
 static gboolean
-link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
-               gboolean ignore_exists, gboolean force,
-               GChecksum **out_checksum,
-               GError **error)
+link_object_trusted (OstreeRepo   *self,
+                     const char   *path,
+                     const char   *checksum,
+                     OstreeObjectType objtype,
+                     gboolean      ignore_exists,
+                     gboolean      force,
+                     gboolean     *did_exist,
+                     GError      **error)
 {
   char *src_basename = NULL;
   char *src_dirname = NULL;
   char *dest_basename = NULL;
   char *tmp_dest_basename = NULL;
   char *dest_dirname = NULL;
-  GChecksum *id = NULL;
   DIR *src_dir = NULL;
   DIR *dest_dir = NULL;
   gboolean ret = FALSE;
-  struct stat stbuf;
   char *dest_path = NULL;
 
   src_basename = g_path_get_basename (path);
@@ -689,9 +735,7 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
       goto out;
     }
 
-  if (!ostree_stat_and_checksum_file (dirfd (src_dir), path, &id, &stbuf, error))
-    goto out;
-  dest_path = prepare_dir_for_checksum_get_object_path (self, id, type, error);
+  dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error);
   if (!dest_path)
     goto out;
 
@@ -719,7 +763,11 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
           ot_util_set_error_from_errno (error, errno);
           goto out;
         }
+      else
+        *did_exist = TRUE;
     }
+  else
+    *did_exist = FALSE;
 
   if (force)
     {
@@ -732,12 +780,8 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
       (void) unlinkat (dirfd (dest_dir), tmp_dest_basename, 0);
     }
 
-  *out_checksum = id;
-  id = NULL;
   ret = TRUE;
  out:
-  if (id != NULL)
-    g_checksum_free (id);
   if (src_dir != NULL)
     closedir (src_dir);
   if (dest_dir != NULL)
@@ -750,12 +794,108 @@ link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
   return ret;
 }
 
+static gboolean
+archive_file_trusted (OstreeRepo   *self,
+                      const char   *path,
+                      const char   *checksum,
+                      OstreeObjectType objtype,
+                      gboolean      ignore_exists,
+                      gboolean      force,
+                      gboolean     *did_exist,
+                      GError      **error)
+{
+  GFile *infile = NULL;
+  GFile *outfile = NULL;
+  GFileOutputStream *out = NULL;
+  gboolean ret = FALSE;
+  char *dest_path = NULL;
+  char *dest_tmp_path = NULL;
+
+  infile = ot_util_new_file_for_path (path);
+
+  dest_path = prepare_dir_for_checksum_get_object_path (self, checksum, objtype, error);
+  if (!dest_path)
+    goto out;
+
+  dest_tmp_path = g_strconcat (dest_path, ".tmp", NULL);
+
+  outfile = ot_util_new_file_for_path (dest_tmp_path);
+  out = g_file_replace (outfile, NULL, FALSE, 0, NULL, error);
+  if (!out)
+    goto out;
+
+  if (!ostree_pack_object ((GOutputStream*)out, infile, objtype, NULL, error))
+    goto out;
+  
+  if (!g_output_stream_close ((GOutputStream*)out, NULL, error))
+    goto out;
+
+  if (rename (dest_tmp_path, dest_path) < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  ret = TRUE;
+ out:
+  g_free (dest_path);
+  g_free (dest_tmp_path);
+  g_clear_object (&infile);
+  g_clear_object (&outfile);
+  g_clear_object (&out);
+  return ret;
+}
+  
+gboolean      
+ostree_repo_store_object_trusted (OstreeRepo   *self,
+                                  const char   *path,
+                                  const char   *checksum,
+                                  OstreeObjectType objtype,
+                                  gboolean      ignore_exists,
+                                  gboolean      force,
+                                  gboolean     *did_exist,
+                                  GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  if (priv->archive && objtype == OSTREE_OBJECT_TYPE_FILE)
+    return archive_file_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error);
+  else
+    return link_object_trusted (self, path, checksum, objtype, ignore_exists, force, did_exist, error);
+}
+
+static gboolean
+link_one_file (OstreeRepo *self, const char *path, OstreeObjectType type,
+               gboolean ignore_exists, gboolean force,
+               GChecksum **out_checksum,
+               GError **error)
+{
+  gboolean ret = FALSE;
+  struct stat stbuf;
+  GChecksum *id = NULL;
+  gboolean did_exist;
+
+  if (!ostree_stat_and_checksum_file (-1, path, type, &id, &stbuf, error))
+    goto out;
+
+  if (!ostree_repo_store_object_trusted (self, path, g_checksum_get_string (id), type,
+                                         ignore_exists, force, &did_exist, error))
+    goto out;
+
+  *out_checksum = id;
+  id = NULL;
+  ret = TRUE;
+ out:
+  if (id != NULL)
+    g_checksum_free (id);
+  return ret;
+}
+
 gboolean
 ostree_repo_link_file (OstreeRepo *self,
-                         const char   *path,
-                         gboolean      ignore_exists,
-                         gboolean      force,
-                         GError      **error)
+                       const char   *path,
+                       gboolean      ignore_exists,
+                       gboolean      force,
+                       GError      **error)
 {
   OstreeRepoPrivate *priv = GET_PRIVATE (self);
   GChecksum *checksum = NULL;
@@ -770,6 +910,227 @@ ostree_repo_link_file (OstreeRepo *self,
   return TRUE;
 }
 
+static gboolean
+unpack_and_checksum_packfile (OstreeRepo   *self,
+                              const char   *path,
+                              gchar       **out_filename,
+                              GChecksum   **out_checksum,
+                              GError      **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  gboolean ret = FALSE;
+  GFile *file = NULL;
+  char *temp_path = NULL;
+  GFile *temp_file = NULL;
+  GFileOutputStream *temp_out = NULL;
+  char *metadata_buf = NULL;
+  GVariant *metadata = NULL;
+  GVariant *xattrs = NULL;
+  GFileInputStream *in = NULL;
+  GChecksum *ret_checksum = NULL;
+  guint32 metadata_len;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  gsize bytes_read, bytes_written;
+  char buf[8192];
+  int temp_fd = -1;
+
+  file = ot_util_new_file_for_path (path);
+
+  in = g_file_read (file, NULL, error);
+  if (!in)
+    goto out;
+      
+  if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error))
+    goto out;
+  if (bytes_read != 4)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; too short while reading metadata length");
+      goto out;
+    }
+      
+  metadata_len = GUINT32_FROM_BE (metadata_len);
+  metadata_buf = g_malloc (metadata_len);
+
+  if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error))
+    goto out;
+  if (bytes_read != metadata_len)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; too short while reading metadata");
+      goto out;
+    }
+
+  metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT),
+                                      metadata_buf, metadata_len, FALSE, NULL, NULL);
+      
+  g_variant_get (metadata, "(uuuu@a(ayay)t)",
+                 &version, &uid, &gid, &mode,
+                 &xattrs, &content_len);
+  uid = GUINT32_FROM_BE (uid);
+  gid = GUINT32_FROM_BE (gid);
+  mode = GUINT32_FROM_BE (mode);
+  content_len = GUINT64_FROM_BE (content_len);
+
+  temp_path = g_build_filename (priv->path, "tmp-packfile-XXXXXX");
+  temp_file = ot_util_new_file_for_path (temp_path);
+      
+  ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  if (S_ISREG (mode))
+    {
+      temp_fd = g_mkstemp (temp_path);
+      if (temp_fd < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+      close (temp_fd);
+      temp_fd = -1;
+      temp_out = g_file_replace (temp_file, NULL, FALSE, 0, NULL, error);
+      if (!temp_out)
+        goto out;
+
+      do
+        {
+          if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+            goto out;
+          g_checksum_update (ret_checksum, (guint8*)buf, bytes_read);
+          if (!g_output_stream_write_all ((GOutputStream*)temp_out, buf, bytes_read, &bytes_written, NULL, error))
+            goto out;
+        }
+      while (bytes_read > 0);
+
+      if (!g_output_stream_close ((GOutputStream*)temp_out, NULL, error))
+        goto out;
+    }
+  else if (S_ISLNK (mode))
+    {
+      g_assert (sizeof (buf) > PATH_MAX);
+
+      if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+        goto out;
+      buf[bytes_read] = '\0';
+      if (symlink (buf, temp_path) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else if (S_ISCHR (mode) || S_ISBLK (mode))
+    {
+      guint32 dev;
+
+      if (!g_input_stream_read_all ((GInputStream*)in, &dev, 4, &bytes_read, NULL, error))
+        goto out;
+      if (bytes_read != 4)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Corrupted packfile; too short while reading device id");
+          goto out;
+        }
+      dev = GUINT32_FROM_BE (dev);
+      if (mknod (temp_path, mode, dev) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted packfile; invalid mode %u", mode);
+      goto out;
+    }
+
+  if (!S_ISLNK (mode))
+    {
+      if (chmod (temp_path, mode) < 0)
+        {
+          ot_util_set_error_from_errno (error, errno);
+          goto out;
+        }
+    }
+
+  if (!ostree_set_xattrs (temp_path, xattrs, error))
+    goto out;
+
+  ostree_checksum_update_stat (ret_checksum, uid, gid, mode);
+  g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+
+  ret = TRUE;
+  *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+  *out_filename = temp_path;
+  temp_path = NULL;
+ out:
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_free (metadata_buf);
+  if (temp_path)
+    (void) unlink (temp_path);
+  g_free (temp_path);
+  g_clear_object (&file);
+  g_clear_object (&in);
+  g_clear_object (&temp_file);
+  g_clear_object (&temp_out);
+  if (metadata)
+   g_variant_unref (metadata);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+
+gboolean
+ostree_repo_store_packfile (OstreeRepo       *self,
+                            const char       *expected_checksum,
+                            const char       *path,
+                            OstreeObjectType  objtype,
+                            GError          **error)
+{
+  gboolean ret = FALSE;
+  char *tempfile = NULL;
+  GChecksum *checksum = NULL;
+  struct stat stbuf;
+  gboolean did_exist;
+
+  if (objtype == OSTREE_OBJECT_TYPE_META)
+    {
+      if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, error))
+        goto out;
+    }
+  else
+    {
+      if (!unpack_and_checksum_packfile (self, path, &tempfile, &checksum, error))
+        goto out;
+
+    }
+
+  if (strcmp (g_checksum_get_string (checksum), expected_checksum) != 0)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Corrupted object %s (actual checksum is %s)",
+                   expected_checksum, g_checksum_get_string (checksum));
+      goto out;
+    }
+
+  if (!ostree_repo_store_object_trusted (self, tempfile ? tempfile : path,
+                                         expected_checksum,
+                                         objtype,
+                                         TRUE, FALSE, &did_exist, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (tempfile)
+    (void) unlink (tempfile);
+  g_free (tempfile);
+  if (checksum)
+    g_checksum_free (checksum);
+  return ret;
+}
+
 typedef struct _ParsedTreeData ParsedTreeData;
 typedef struct _ParsedDirectoryData ParsedDirectoryData;
 
@@ -839,6 +1200,7 @@ parse_tree (OstreeRepo    *self,
                              sha256, &tree_variant, error))
     goto out;
 
+  /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */
   g_variant_get (tree_variant, "(u@a{sv}@a(ss)@a(sss))",
                  &version, &meta_variant, &files_variant, &dirs_variant);
 
@@ -922,6 +1284,7 @@ load_commit_and_trees (OstreeRepo   *self,
                              commit_sha256, &ret_commit, error))
     goto out;
 
+  /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */
   g_variant_get_child (ret_commit, 6, "&s", &tree_contents_checksum);
   g_variant_get_child (ret_commit, 7, "&s", &tree_meta_checksum);
 
@@ -1366,6 +1729,18 @@ add_files_to_tree_and_import (OstreeRepo   *self,
   return ret;
 }
 
+gboolean      
+ostree_repo_write_ref (OstreeRepo  *self,
+                       gboolean     is_local,
+                       const char  *name,
+                       const char  *rev,
+                       GError     **error)
+{
+  OstreeRepoPrivate *priv = GET_PRIVATE (self);
+  return write_checksum_file (is_local ? priv->local_heads_dir : priv->remote_heads_dir, 
+                              name, rev, error);
+}
+
 static gboolean
 commit_parsed_tree (OstreeRepo *self,
                     const char   *branch,
@@ -1377,7 +1752,6 @@ commit_parsed_tree (OstreeRepo *self,
                     GChecksum   **out_commit,
                     GError      **error)
 {
-  OstreeRepoPrivate *priv = GET_PRIVATE (self);
   gboolean ret = FALSE;
   GChecksum *root_checksum = NULL;
   GChecksum *ret_commit = NULL;
@@ -1403,7 +1777,7 @@ commit_parsed_tree (OstreeRepo *self,
                                commit, &ret_commit, error))
     goto out;
 
-  if (!write_checksum_file (priv->local_heads_dir, branch, g_checksum_get_string (ret_commit), error))
+  if (!ostree_repo_write_ref (self, TRUE, branch, g_checksum_get_string (ret_commit), error))
     goto out;
 
   ret = TRUE;
@@ -1648,7 +2022,8 @@ iter_object_dir (OstreeRepo   *self,
       
       if (type != G_FILE_TYPE_DIRECTORY
           && (g_str_has_suffix (name, ".meta")
-              || g_str_has_suffix (name, ".file")))
+              || g_str_has_suffix (name, ".file")
+              || g_str_has_suffix (name, ".packfile")))
         {
           char *dot;
           char *path;
@@ -1789,6 +2164,7 @@ checkout_one_directory (OstreeRepo  *self,
 
   dest_path = g_build_filename (destination, dirname, NULL);
       
+  /* PARSE OSTREE_SERIALIZED_DIRMETA_VARIANT */
   g_variant_get (dir->meta_data, "(uuuu@a(ayay))",
                  &version, &uid, &gid, &mode,
                  &xattr_variant);
index c0159ea4f289c934555400bd4a4735902ae856c1..15d5036aa6208628ae1cb9f7442131dc52e1e422 100644 (file)
@@ -53,17 +53,50 @@ OstreeRepo* ostree_repo_new (const char *path);
 
 gboolean      ostree_repo_check (OstreeRepo  *self, GError **error);
 
+const char *  ostree_repo_get_path (OstreeRepo  *self);
+
+gboolean      ostree_repo_is_archive (OstreeRepo  *self);
+
+GKeyFile *    ostree_repo_get_config (OstreeRepo *self);
+
+GKeyFile *    ostree_repo_copy_config (OstreeRepo *self);
+
+gboolean      ostree_repo_write_config (OstreeRepo *self,
+                                        GKeyFile   *new_config,
+                                        GError    **error);
+
 gboolean      ostree_repo_link_file (OstreeRepo *self,
-                                       const char   *path,
-                                       gboolean      ignore_exists,
-                                       gboolean      force,
-                                       GError      **error);
+                                     const char   *path,
+                                     gboolean      ignore_exists,
+                                     gboolean      force,
+                                     GError      **error);
+
+gboolean      ostree_repo_store_packfile (OstreeRepo       *self,
+                                           const char       *expected_checksum,
+                                           const char       *path,
+                                           OstreeObjectType  objtype,
+                                           GError          **error);
+
+gboolean      ostree_repo_store_object_trusted (OstreeRepo   *self,
+                                                const char   *path,
+                                                const char   *checksum,
+                                                OstreeObjectType objtype,
+                                                gboolean      ignore_exists,
+                                                gboolean      force,
+                                                gboolean     *did_exist,
+                                                GError      **error);
 
 gboolean      ostree_repo_resolve_rev (OstreeRepo  *self,
                                        const char  *rev,
                                        char       **out_resolved,
                                        GError     **error);
 
+gboolean      ostree_repo_write_ref (OstreeRepo  *self,
+                                     gboolean     is_local,
+                                     const char  *name,
+                                     const char  *rev,
+                                     GError     **error);
+
 gboolean      ostree_repo_load_variant (OstreeRepo *self,
                                           const char   *sha256,
                                           OstreeSerializedVariantType *out_type,
index f32e741b03d97d1396a0336a726f823149404a6b..ad5dfd8f1443ec0f10c19ab9a07b902f3cee66ad 100644 (file)
@@ -33,6 +33,7 @@ static OstreeBuiltin builtins[] = {
   { "commit", ostree_builtin_commit, 0 },
   { "link-file", ostree_builtin_link_file, 0 },
   { "log", ostree_builtin_log, 0 },
+  { "pull", ostree_builtin_pull, 0 },
   { "fsck", ostree_builtin_fsck, 0 },
   { "remote", ostree_builtin_remote, 0 },
   { "rev-parse", ostree_builtin_rev_parse, 0 },
diff --git a/src/ostree-http-backend.c b/src/ostree-http-backend.c
new file mode 100644 (file)
index 0000000..ebde526
--- /dev/null
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include <libsoup/soup-gnome.h>
+#include <ostree.h>
+
+#include <string.h>
+
+int
+main (int     argc,
+      char  **argv)
+{
+  const char *repo_root;
+
+  g_type_init ();
+
+  repo_root = g_getenv ("OSTREE_REPO_PREFIX");
+  if (!repo_root)
+    {
+      g_printerr ("OSTREE_REPO_PREFIX not set\n");
+      return 1;
+    }
+
+  return 0;
+}
index 766d17fecb2615ca343333b3aea820725465c983..3a2175249e5df6f2c0c5abf16610a80c0082accb 100644 (file)
@@ -39,6 +39,82 @@ typedef struct {
   guint n_objects;
 } HtFsckData;
 
+static gboolean
+checksum_packed_file (HtFsckData   *data,
+                      const char   *path,
+                      GChecksum   **out_checksum,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  GChecksum *ret_checksum = NULL;
+  GFile *file = NULL;
+  char *metadata_buf = NULL;
+  GVariant *metadata = NULL;
+  GVariant *xattrs = NULL;
+  GFileInputStream *in = NULL;
+  guint32 metadata_len;
+  guint32 version, uid, gid, mode;
+  guint64 content_len;
+  gsize bytes_read;
+  char buf[8192];
+
+  file = ot_util_new_file_for_path (path);
+
+  in = g_file_read (file, NULL, error);
+  if (!in)
+    goto out;
+      
+  if (!g_input_stream_read_all ((GInputStream*)in, &metadata_len, 4, &bytes_read, NULL, error))
+    goto out;
+      
+  metadata_len = GUINT32_FROM_BE (metadata_len);
+      
+  metadata_buf = g_malloc (metadata_len);
+      
+  if (!g_input_stream_read_all ((GInputStream*)in, metadata_buf, metadata_len, &bytes_read, NULL, error))
+    goto out;
+
+  metadata = g_variant_new_from_data (G_VARIANT_TYPE (OSTREE_PACK_FILE_VARIANT_FORMAT),
+                                      metadata_buf, metadata_len, FALSE, NULL, NULL);
+      
+  g_variant_get (metadata, "(uuuu@a(ayay)t)",
+                 &version, &uid, &gid, &mode,
+                 &xattrs, &content_len);
+  uid = GUINT32_FROM_BE (uid);
+  gid = GUINT32_FROM_BE (gid);
+  mode = GUINT32_FROM_BE (mode);
+  content_len = GUINT64_FROM_BE (content_len);
+
+  ret_checksum = g_checksum_new (G_CHECKSUM_SHA256);
+
+  do
+    {
+      if (!g_input_stream_read_all ((GInputStream*)in, buf, sizeof(buf), &bytes_read, NULL, error))
+        goto out;
+      g_checksum_update (ret_checksum, (guint8*)buf, bytes_read);
+    }
+  while (bytes_read > 0);
+
+  ostree_checksum_update_stat (ret_checksum, uid, gid, mode);
+  g_checksum_update (ret_checksum, (guint8*)g_variant_get_data (xattrs), g_variant_get_size (xattrs));
+
+  ret = TRUE;
+  *out_checksum = ret_checksum;
+  ret_checksum = NULL;
+ out:
+  if (ret_checksum)
+    g_checksum_free (ret_checksum);
+  g_free (metadata_buf);
+  g_clear_object (&file);
+  g_clear_object (&in);
+  if (metadata)
+   g_variant_unref (metadata);
+  if (xattrs)
+    g_variant_unref (xattrs);
+  return ret;
+}
+                    
+
 static void
 object_iter_callback (OstreeRepo  *repo,
                       const char    *path,
@@ -53,25 +129,46 @@ object_iter_callback (OstreeRepo  *repo,
   char *checksum_prefix = NULL;
   char *checksum_string = NULL;
   char *filename_checksum = NULL;
+  gboolean packed = FALSE;
+  OstreeObjectType objtype;
   char *dot;
 
-  dirname = g_path_get_dirname (path);
-  checksum_prefix = g_path_get_basename (dirname);
-  
   /* nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink");
      if (nlinks < 2 && !quiet)
      g_printerr ("note: floating object: %s\n", path); */
 
-  if (!ostree_stat_and_checksum_file (-1, path, &checksum, &stbuf, &error))
-    goto out;
+  if (g_str_has_suffix (path, ".meta"))
+    objtype = OSTREE_OBJECT_TYPE_META;
+  else if (g_str_has_suffix (path, ".file"))
+    objtype = OSTREE_OBJECT_TYPE_FILE;
+  else if (g_str_has_suffix (path, ".packfile"))
+    {
+      objtype = OSTREE_OBJECT_TYPE_FILE;
+     packed = TRUE;
+    }
+  else
+    g_assert_not_reached ();
+
+  if (packed && objtype == OSTREE_OBJECT_TYPE_FILE)
+    {
+      if (!checksum_packed_file (data, path, &checksum, &error))
+        goto out;
+    }
+  else
+    {
+      if (!ostree_stat_and_checksum_file (-1, path, objtype, &checksum, &stbuf, &error))
+        goto out;
+    }
 
   filename_checksum = g_strdup (g_file_info_get_name (file_info));
   dot = strrchr (filename_checksum, '.');
   g_assert (dot != NULL);
   *dot = '\0';
-
+  
+  dirname = g_path_get_dirname (path);
+  checksum_prefix = g_path_get_basename (dirname);
   checksum_string = g_strconcat (checksum_prefix, filename_checksum, NULL);
-
+  
   if (strcmp (checksum_string, g_checksum_get_string (checksum)) != 0)
     {
       g_printerr ("ERROR: corrupted object '%s' expected checksum: %s\n",
index ef4530b6241bb2049f37ec339c61454addcd3d2f..d16b57c801b35b776c8bb48df327c33170824be2 100644 (file)
 #include <glib/gi18n.h>
 
 static char *repo_path;
+static gboolean archive;
+
 static GOptionEntry options[] = {
   { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", NULL },
+  { "archive", 0, 0, G_OPTION_ARG_NONE, &archive, "Initialize repository as archive", NULL },
   { NULL }
 };
 
@@ -44,6 +47,7 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   GFile *repodir = NULL;
   GFile *child = NULL;
   GFile *grandchild = NULL;
+  GString *config_data = NULL;
 
   context = g_option_context_new ("- Initialize a new empty repository");
   g_option_context_add_main_entries (context, options, NULL);
@@ -54,12 +58,15 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   if (repo_path == NULL)
     repo_path = ".";
 
-  repodir = g_file_new_for_path (repo_path);
+  repodir = ot_util_new_file_for_path (repo_path);
 
   child = g_file_get_child (repodir, "config");
+
+  config_data = g_string_new (DEFAULT_CONFIG_CONTENTS);
+  g_string_append_printf (config_data, "archive=%s\n", archive ? "true" : "false");
   if (!g_file_replace_contents (child,
-                                DEFAULT_CONFIG_CONTENTS,
-                                strlen (DEFAULT_CONFIG_CONTENTS),
+                                config_data->str,
+                                config_data->len,
                                 NULL, FALSE, 0, NULL,
                                 NULL, error))
     goto out;
@@ -73,18 +80,20 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
   child = g_file_get_child (repodir, "refs");
   if (!g_file_make_directory (child, NULL, error))
     goto out;
+
   grandchild = g_file_get_child (child, "heads");
   if (!g_file_make_directory (grandchild, NULL, error))
     goto out;
-  g_clear_object (&child);
   g_clear_object (&grandchild);
 
-  child = g_file_get_child (repodir, "tags");
-  if (!g_file_make_directory (child, NULL, error))
+  grandchild = g_file_get_child (child, "remotes");
+  if (!g_file_make_directory (grandchild, NULL, error))
     goto out;
+  g_clear_object (&grandchild);
+
   g_clear_object (&child);
 
-  child = g_file_get_child (repodir, "remotes");
+  child = g_file_get_child (repodir, "tags");
   if (!g_file_make_directory (child, NULL, error))
     goto out;
   g_clear_object (&child);
@@ -93,6 +102,8 @@ ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error)
  out:
   if (context)
     g_option_context_free (context);
+  if (config_data)
+    g_string_free (config_data, TRUE);
   g_clear_object (&repodir);
   g_clear_object (&child);
   g_clear_object (&grandchild);
diff --git a/src/ot-builtin-pull.c b/src/ot-builtin-pull.c
new file mode 100644 (file)
index 0000000..a9ba6e0
--- /dev/null
@@ -0,0 +1,365 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+#include <libsoup/soup-gnome.h>
+
+static char *repo_path;
+
+static GOptionEntry options[] = {
+  { "repo", 0, 0, G_OPTION_ARG_FILENAME, &repo_path, "Repository path", "repo" },
+  { NULL }
+};
+
+static void
+usage_error (GOptionContext *context, const char *message, GError **error)
+{
+  gchar *help = g_option_context_get_help (context, TRUE, NULL);
+  g_printerr ("%s\n", help);
+  g_free (help);
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       message);
+}
+
+static gboolean
+fetch_uri (OstreeRepo  *repo,
+           SoupSession *soup,
+           SoupURI     *uri,
+           char       **temp_filename,
+           GError     **error)
+{
+  gboolean ret = FALSE;
+  SoupMessage *msg = NULL;
+  guint response;
+  char *template = NULL;
+  int fd;
+  SoupBuffer *buf = NULL;
+  GFile *tempf = NULL;
+  
+  msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+  
+  response = soup_session_send_message (soup, msg);
+  if (response != 200)
+    {
+      char *uri_string = soup_uri_to_string (uri, FALSE);
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to retrieve '%s': %d %s",
+                   uri_string, response, msg->reason_phrase);
+      g_free (uri_string);
+      goto out;
+    }
+
+  template = g_strdup_printf ("%s/tmp-fetchXXXXXX", ostree_repo_get_path (repo));
+  
+  fd = g_mkstemp (template);
+  if (fd < 0)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+  close (fd);
+  tempf = ot_util_new_file_for_path (template);
+
+  buf = soup_message_body_flatten (msg->response_body);
+
+  if (!g_file_replace_contents (tempf, buf->data, buf->length, NULL, FALSE, 0, NULL, NULL, error))
+    goto out;
+  
+  *temp_filename = template;
+  template = NULL;
+
+  ret = TRUE;
+ out:
+  g_free (template);
+  g_clear_object (&msg);
+  g_clear_object (&tempf);
+  return ret;
+}
+
+static gboolean
+store_object (OstreeRepo  *repo,
+              SoupSession *soup,
+              SoupURI     *baseuri,
+              const char  *object,
+              OstreeObjectType objtype,
+              gboolean    *did_exist,
+              GError     **error)
+{
+  gboolean ret = FALSE;
+  char *filename = NULL;
+  char *objpath = NULL;
+  char *relpath = NULL;
+  SoupURI *obj_uri = NULL;
+
+  objpath = ostree_get_relative_object_path (object, objtype, TRUE);
+  obj_uri = soup_uri_copy (baseuri);
+  relpath = g_build_filename (soup_uri_get_path (obj_uri), objpath, NULL);
+  soup_uri_set_path (obj_uri, relpath);
+
+  if (!fetch_uri (repo, soup, obj_uri, &filename, error))
+    goto out;
+
+  if (!ostree_repo_store_packfile (repo, object, filename, objtype, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (obj_uri)
+    soup_uri_free (obj_uri);
+  if (filename)
+    (void) unlink (filename);
+  g_free (filename);
+  g_free (objpath);
+  g_free (relpath);
+  return ret;
+}
+
+static gboolean
+store_tree_recurse (OstreeRepo   *repo,
+                    SoupSession  *soup,
+                    SoupURI      *base_uri,
+                    const char   *rev,
+                    GError      **error)
+{
+  gboolean ret = FALSE;
+  GVariant *tree = NULL;
+  GVariant *files_variant = NULL;
+  GVariant *dirs_variant = NULL;
+  OstreeSerializedVariantType metatype;
+  gboolean did_exist;
+  int i, n;
+
+  if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+    goto out;
+
+  if (!did_exist)
+    {
+      if (!ostree_repo_load_variant (repo, rev, &metatype, &tree, error))
+        goto out;
+      
+      if (metatype != OSTREE_SERIALIZED_TREE_VARIANT)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Tree metadata '%s' has wrong type %d, expected %d",
+                       rev, metatype, OSTREE_SERIALIZED_TREE_VARIANT);
+          goto out;
+        }
+      
+      /* PARSE OSTREE_SERIALIZED_TREE_VARIANT */
+      g_variant_get_child (tree, 2, "@a(ss)", &files_variant);
+      g_variant_get_child (tree, 3, "@a(sss)", &dirs_variant);
+      
+      n = g_variant_n_children (files_variant);
+      for (i = 0; i < n; i++)
+        {
+          const char *filename;
+          const char *checksum;
+
+          g_variant_get_child (files_variant, i, "(ss)", &filename, &checksum);
+
+          if (!store_object (repo, soup, base_uri, checksum, OSTREE_OBJECT_TYPE_FILE, &did_exist, error))
+            goto out;
+        }
+      
+      for (i = 0; i < n; i++)
+        {
+          const char *dirname;
+          const char *tree_checksum;
+          const char *meta_checksum;
+
+          g_variant_get_child (dirs_variant, i, "(sss)",
+                               &dirname, &tree_checksum, &meta_checksum);
+
+          if (!store_tree_recurse (repo, soup, base_uri, tree_checksum, error))
+            goto out;
+
+          if (!store_object (repo, soup, base_uri, meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+            goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (tree)
+    g_variant_unref (tree);
+  if (files_variant)
+    g_variant_unref (files_variant);
+  if (dirs_variant)
+    g_variant_unref (dirs_variant);
+  return ret;
+}
+
+static gboolean
+store_commit_recurse (OstreeRepo   *repo,
+                      SoupSession  *soup,
+                      SoupURI      *base_uri,
+                      const char   *rev,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  GVariant *commit = NULL;
+  OstreeSerializedVariantType metatype;
+  const char *tree_contents_checksum;
+  const char *tree_meta_checksum;
+  gboolean did_exist;
+
+  if (!store_object (repo, soup, base_uri, rev, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+    goto out;
+
+  if (!did_exist)
+    {
+      if (!ostree_repo_load_variant (repo, rev, &metatype, &commit, error))
+        goto out;
+      
+      if (metatype != OSTREE_SERIALIZED_COMMIT_VARIANT)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Commit '%s' has wrong type %d, expected %d",
+                       rev, metatype, OSTREE_SERIALIZED_COMMIT_VARIANT);
+          goto out;
+        }
+      
+      /* PARSE OSTREE_SERIALIZED_COMMIT_VARIANT */
+      g_variant_get_child (commit, 6, "&s", &tree_contents_checksum);
+      g_variant_get_child (commit, 7, "&s", &tree_meta_checksum);
+      
+      if (!store_object (repo, soup, base_uri, tree_meta_checksum, OSTREE_OBJECT_TYPE_META, &did_exist, error))
+        goto out;
+      
+      if (!store_tree_recurse (repo, soup, base_uri, tree_contents_checksum, error))
+        goto out;
+    }
+
+  ret = TRUE;
+ out:
+  if (commit)
+    g_variant_unref (commit);
+  return ret;
+}
+                      
+gboolean
+ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error)
+{
+  GOptionContext *context;
+  gboolean ret = FALSE;
+  OstreeRepo *repo = NULL;
+  const char *remote;
+  const char *branch;
+  char *remote_branch_ref_path = NULL;
+  char *key = NULL;
+  char *baseurl = NULL;
+  char *refpath = NULL;
+  char *temppath = NULL;
+  GKeyFile *config = NULL;
+  SoupURI *base_uri = NULL;
+  SoupURI *target_uri = NULL;
+  SoupSession *soup = NULL;
+  char *rev = NULL;
+
+  context = g_option_context_new ("REMOTE BRANCH - Download data from remote repository");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (repo_path == NULL)
+    repo_path = ".";
+
+  repo = ostree_repo_new (repo_path);
+  if (!ostree_repo_check (repo, error))
+    goto out;
+
+  if (argc < 3)
+    {
+      usage_error (context, "REMOTE and BRANCH must be specified", error);
+      goto out;
+    }
+
+  remote = argv[1];
+  branch = argv[2];
+
+  config = ostree_repo_get_config (repo);
+
+  key = g_strdup_printf ("remote \"%s\"", remote);
+  baseurl = g_key_file_get_string (config, key, "url", error);
+  if (!baseurl)
+    goto out;
+  base_uri = soup_uri_new (baseurl);
+  if (!base_uri)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to parse url '%s'", baseurl);
+      goto out;
+    }
+  target_uri = soup_uri_copy (base_uri);
+  g_free (refpath);
+  refpath = g_build_filename (soup_uri_get_path (target_uri), "refs", "heads", branch, NULL);
+  soup_uri_set_path (target_uri, refpath);
+  
+  soup = soup_session_sync_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ",
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
+                                             SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR,
+                                             NULL);
+  if (!fetch_uri (repo, soup, target_uri, &temppath, error))
+    goto out;
+
+  rev = ot_util_get_file_contents_utf8 (temppath, error);
+  if (!rev)
+    goto out;
+  g_strchomp (rev);
+
+  if (!ostree_validate_checksum_string (rev, error))
+    goto out;
+
+  if (!store_commit_recurse (repo, soup, base_uri, rev, error))
+    goto out;
+
+  if (!ostree_repo_write_ref (repo, FALSE, branch, rev, error))
+    goto out;
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  if (temppath)
+    (void) unlink (temppath);
+  g_free (temppath);
+  g_free (key);
+  g_free (rev);
+  g_free (baseurl);
+  g_free (refpath);
+  g_free (remote_branch_ref_path);
+  g_clear_object (&soup);
+  if (base_uri)
+    soup_uri_free (base_uri);
+  if (target_uri)
+    soup_uri_free (target_uri);
+  g_clear_object (&repo);
+  g_clear_object (&soup);
+  return ret;
+}
index f26d683adb1aa8549f38828204d677d19c2cc310..40f7c295113f320b82cfc7bacd7b6893a3a26e23 100644 (file)
@@ -51,9 +51,6 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
   OstreeRepo *repo = NULL;
   OstreeCheckout *checkout = NULL;
   const char *op;
-  gsize len;
-  char *config_path = NULL;
-  char *data = NULL;
   GKeyFile *config = NULL;
 
   context = g_option_context_new ("OPERATION [args] - Control remote repository configuration");
@@ -77,10 +74,7 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
 
   op = argv[1];
 
-  config = g_key_file_new ();
-  config_path = g_build_filename (repo_path, "config", NULL);
-  if (!g_key_file_load_from_file (config, config_path, 0, error))
-    goto out;
+  config = ostree_repo_copy_config (repo);
 
   if (!strcmp (op, "add"))
     {
@@ -92,25 +86,23 @@ ostree_builtin_remote (int argc, char **argv, const char *prefix, GError **error
         }
       key = g_strdup_printf ("remote \"%s\"", argv[2]);
       g_key_file_set_string (config, key, "url", argv[3]);
+      g_free (key);
     }
   else
     {
       usage_error (context, "Unknown operation", error);
       goto out;
     }
-  data = g_key_file_to_data (config, &len, error);
-  if (!g_file_set_contents (config_path, data, len, error))
-    goto out;
 
+  if (!ostree_repo_write_config (repo, config, error))
+    goto out;
   ret = TRUE;
  out:
   if (context)
     g_option_context_free (context);
   if (config)
     g_key_file_unref (config);
-  g_free (data);
-  g_free (config_path);
   g_clear_object (&repo);
   g_clear_object (&checkout);
   return ret;
index 1da3db9bb7cb4eb14f554f4a2849884a5c3f3db0..1373f6018e9fb7b8c7ea81c9dc82f5152ccc1639 100644 (file)
@@ -41,6 +41,7 @@ gboolean ostree_builtin_commit (int argc, char **argv, const char *prefix, GErro
 gboolean ostree_builtin_init (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_log (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_link_file (int argc, char **argv, const char *prefix, GError **error);
+gboolean ostree_builtin_pull (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_run_triggers (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_fsck (int argc, char **argv, const char *prefix, GError **error);
 gboolean ostree_builtin_show (int argc, char **argv, const char *prefix, GError **error);
index 24600083db41748be192c1a6cc2b90e6ba21c012..8a94c781a2e02140a56217e5ffa075991f3fd27e 100644 (file)
@@ -1 +1,4 @@
 !Makefile
+ostree-http-server
+run-apache
+tmpdir-lifecycle
index c24db2c77476d3e7e8bd39f40854146759ada5ae..6beba04e5f38190d3de3b5cb39e1e3a6d8c99ca2 100644 (file)
 
 TESTS = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 
-all:
+all: tmpdir-lifecycle run-apache
+
+tmpdir-lifecycle: tmpdir-lifecycle.c Makefile
+       gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
+
+run-apache: run-apache.c Makefile
+       gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
 
 check:
        @for test in $(TESTS); do \
index cedf5370e98d8852b6ac0e3a28305016918b21b7..9601ec274380d7e66ae9472f6e007d523ca7ed1b 100644 (file)
@@ -18,6 +18,9 @@
 #
 # Author: Colin Walters <walters@verbum.org>
 
+cd `dirname $0`
+SRCDIR=`pwd`
+cd -
 TMPDIR=${TMPDIR:-/tmp}
 export TMPDIR
 test_tmpdir=`mktemp -d "$TMPDIR/ostree-tests.XXXXXXXXXX"`
@@ -91,4 +94,53 @@ setup_test_repository2 () {
     ostree fsck -q $ot_repo
 }
 
+setup_fake_remote_repo1() {
+    oldpwd=`pwd`
+    mkdir ostree-srv
+    cd ostree-srv
+    mkdir gnomerepo
+    ostree init --archive --repo=gnomerepo
+    mkdir gnomerepo-files
+    cd gnomerepo-files 
+    echo first > firstfile
+    mkdir baz
+    echo moo > baz/cow
+    echo alien > baz/saucer
+    find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "A remote commit" -m "Some Commit body" --from-stdin
+    mkdir baz/deeper
+    ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "Add deeper" --add=baz/deeper
+    echo hi > baz/deeper/ohyeah
+    mkdir baz/another/
+    echo x > baz/another/y
+    find | grep -v '^\.$' | ostree commit --repo=${test_tmpdir}/ostree-srv/gnomerepo -b main -s "The rest" --from-stdin
+    cd ..
+    rm -rf gnomerepo-files
+    
+    cd ${test_tmpdir}
+    mkdir ${test_tmpdir}/httpd
+    cd httpd
+    cp $(command -v ostree-http-backend) .
+    chmod a+x ostree-http-backend
+    cat >httpd.conf <<EOF
+ServerRoot ${test_tmpdir}/httpd
+PidFile pid
+LogLevel crit
+ErrorLog log
+LockFile lock
+ServerName localhost
+
+LoadModule alias_module modules/mod_alias.so
+LoadModule cgi_module modules/mod_cgi.so
+LoadModule env_module modules/mod_env.so
+
+StartServers 1
+
+# SetEnv OSTREE_REPO_PREFIX ${test_tmpdir}/ostree-srv
+Alias /ostree/ ${test_tmpdir}/ostree-srv/
+# ScriptAlias /ostree/  ${test_tmpdir}/httpd/ostree-http-backend/
+EOF
+    ${SRCDIR}/tmpdir-lifecycle ${SRCDIR}/run-apache `pwd`/httpd.conf ${test_tmpdir}/httpd-address
+    cd ${oldpwd} 
+}
+
 trap 'die' EXIT
diff --git a/tests/ostree-http-server.c b/tests/ostree-http-server.c
deleted file mode 100644 (file)
index 63edda5..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include <libsoup/soup-gnome.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdlib.h>
-
-static void
-request_callback (SoupServer *server, SoupMessage *msg,
-                 const char *path, GHashTable *query,
-                 SoupClientContext *context, gpointer data)
-{
-  if (msg->method == SOUP_METHOD_GET) 
-    {
-      GFile *file;
-      char *content;
-      gsize len;
-      
-      /* Strip leading / */
-      file = g_file_new_for_path (path + 1);
-      
-      if (g_file_load_contents (file, NULL, &content, &len, NULL, NULL))
-       soup_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_TAKE, content, len);
-      else
-       soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
-    }
-  else
-    {
-      soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
-    }
-}
-
-static void
-on_dir_changed (GFileMonitor  *mon,
-               GFile *file,
-               GFile *other,
-               GFileMonitorEvent  event,
-               gpointer user_data)
-{
-  GMainLoop *loop = user_data;
-
-  if (event == G_FILE_MONITOR_EVENT_DELETED)
-    g_main_loop_quit (loop);
-}
-
-int
-main (int     argc,
-      char  **argv)
-{
-  SoupAddress *addr;
-  SoupServer *server;
-  GMainLoop *loop;
-  GFileMonitor *monitor;
-  GFile *curdir;
-  GError *error = NULL;
-
-  g_type_init ();
-
-  loop = g_main_loop_new (NULL, TRUE);
-
-  curdir = g_file_new_for_path (".");
-  monitor = g_file_monitor_directory (curdir, 0, NULL, &error);
-  if (!monitor)
-    exit (1);
-  g_signal_connect (monitor, "changed",
-                   G_CALLBACK (on_dir_changed), loop);
-
-  addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT);
-  soup_address_resolve_sync (addr, NULL);
-
-  server = soup_server_new (SOUP_SERVER_INTERFACE, addr,
-                           SOUP_SERVER_ASYNC_CONTEXT, g_main_loop_get_context (loop),
-                           NULL);
-  soup_server_add_handler (server, NULL,
-                          request_callback,
-                          NULL, NULL);
-
-  soup_server_run_async (server);
-
-  g_print ("http://127.0.0.1:%ld\n", (long)soup_server_get_port (server));
-
-  g_main_loop_run (loop);
-
-  return 0;
-}
diff --git a/tests/run-apache.c b/tests/run-apache.c
new file mode 100644 (file)
index 0000000..b5f7e5c
--- /dev/null
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include <gio/gio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+
+/* Taken from gnome-user-share src/httpd.c under the GPLv2 */
+static int
+get_port (void)
+{
+  int sock;
+  int saved_errno;
+  struct sockaddr_in addr;
+  int reuse;
+  socklen_t len;
+
+  sock = socket (PF_INET, SOCK_STREAM, 0);
+  if (sock < 0)
+    {
+      return -1;
+    }
+  
+  memset (&addr, 0, sizeof (addr));
+  addr.sin_family = AF_INET;
+  addr.sin_port = 0;
+  addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+
+  reuse = 1;
+  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+  if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1)
+    {
+      saved_errno = errno;
+      close (sock);
+      errno = saved_errno;
+      return -1;
+    }
+
+  len = sizeof (addr);
+  if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1)
+    {
+      saved_errno = errno;
+      close (sock);
+      errno = saved_errno;
+      return -1;
+    }
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+  /* XXX This exposes a potential race condition, but without this,
+   * httpd will not start on the above listed platforms due to the fact
+   * that SO_REUSEADDR is also needed when Apache binds to the listening
+   * socket.  At this time, Apache does not support that socket option.
+   */
+  close (sock);
+#endif
+  return ntohs (addr.sin_port);
+}
+
+static const char *known_httpd_modules_locations [] = {
+  "/usr/libexec/apache2",
+  "/usr/lib/apache2/modules",
+  "/usr/lib64/httpd/modules",
+  "/usr/lib/httpd/modules",
+  NULL
+};
+
+static gchar*
+get_httpd_modules_path ()
+{
+  int i;
+
+  for (i = 0; known_httpd_modules_locations[i]; i++)
+    {
+      if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE)
+         && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR))
+       {
+         return g_strdup (known_httpd_modules_locations[i]);
+       }
+    }
+  return NULL;
+}
+
+int
+main (int     argc,
+      char  **argv)
+{
+  int port;
+  char *listen;
+  char *address_string;
+  GError *error = NULL;
+  GPtrArray *httpd_argv;
+  char *modules;
+
+  if (argc != 3)
+    {
+      fprintf (stderr, "usage: run-apache CONF PORTFILE");
+      return 1;
+    }
+
+  g_type_init ();
+
+  port = get_port ();
+  if (port == -1)
+    {
+      perror ("Failed to bind port");
+      return 1;
+    }
+
+  httpd_argv = g_ptr_array_new ();
+  g_ptr_array_add (httpd_argv, "httpd");
+  g_ptr_array_add (httpd_argv, "-f");
+  g_ptr_array_add (httpd_argv, argv[1]);
+  g_ptr_array_add (httpd_argv, "-C");
+  listen = g_strdup_printf ("Listen 127.0.0.1:%d", port);
+  g_ptr_array_add (httpd_argv, listen);
+  g_ptr_array_add (httpd_argv, NULL);
+
+  address_string = g_strdup_printf ("http://127.0.0.1:%d\n", port);
+  
+  if (!g_file_set_contents (argv[2], address_string, -1, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      return 1;
+    }
+
+  setenv ("LANG", "C", 1);
+  modules = get_httpd_modules_path ();
+  if (modules == NULL)
+    {
+      g_printerr ("Failed to find httpd modules\n");
+      return 1;
+    }
+  if (symlink (modules, "modules") < 0)
+    {
+      perror ("failed to make modules symlink");
+      return 1;
+    }
+  execvp ("httpd", (char**)httpd_argv->pdata);
+  perror ("Failed to run httpd");
+  return 1;
+}
diff --git a/tests/t0012-pull.sh b/tests/t0012-pull.sh
new file mode 100755 (executable)
index 0000000..a0161a4
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Author: Colin Walters <walters@verbum.org>
+
+set -e
+
+. libtest.sh
+
+echo '1..1'
+
+setup_fake_remote_repo1
+cd ${test_tmpdir}
+mkdir repo
+ostree init --repo=repo
+ostree remote --repo=repo add origin $(cat httpd-address)/ostree/gnomerepo
+ostree pull --repo=repo origin main
+echo "ok pull"
diff --git a/tests/t0013-commit-archive.sh b/tests/t0013-commit-archive.sh
new file mode 100755 (executable)
index 0000000..7f6ffc9
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Author: Colin Walters <walters@verbum.org>
+
+set -e
+
+. libtest.sh
+
+echo '1..4'
+
+mkdir files
+cd files
+echo first > firstfile
+echo second > secondfile
+ln -s foo bar
+
+mkdir ../repo
+repo="--repo=../repo"
+ostree init --archive $repo
+echo 'ok init'
+ostree commit $repo -b test -s "Test Commit 1" -m "Commit body first" --add=firstfile
+echo 'ok commit 1'
+ostree commit $repo -b test -s "Test Commit 2" -m "Commit body first" --add=secondfile --add=bar
+echo 'ok commit 2'
+ostree fsck -q $repo
+echo 'ok fsck'
diff --git a/tests/tmpdir-lifecycle.c b/tests/tmpdir-lifecycle.c
new file mode 100644 (file)
index 0000000..95aae63
--- /dev/null
@@ -0,0 +1,103 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Kill a child process when the current directory is deleted
+ *
+ * Copyright (C) 2011 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include <gio/gio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct TmpdirLifecyleData {
+  GMainLoop *loop;
+  GPid pid;
+  gboolean exited;
+};
+
+static void
+on_dir_changed (GFileMonitor  *mon,
+               GFile *file,
+               GFile *other,
+               GFileMonitorEvent  event,
+               gpointer user_data)
+{
+  struct TmpdirLifecyleData *data = user_data;
+
+  if (event == G_FILE_MONITOR_EVENT_DELETED)
+    g_main_loop_quit (data->loop);
+}
+
+static void
+on_child_exited (GPid  pid,
+                 int status,
+                 gpointer user_data)
+{
+  struct TmpdirLifecyleData *data = user_data;
+
+  data->exited = TRUE;
+  g_main_loop_quit (data->loop);
+}
+
+int
+main (int     argc,
+      char  **argv)
+{
+  GFileMonitor *monitor;
+  GFile *curdir;
+  GError *error = NULL;
+  GPtrArray *new_argv;
+  int i;
+  GPid pid;
+  struct TmpdirLifecyleData data;
+
+  g_type_init ();
+
+  memset (&data, 0, sizeof (data));
+
+  data.loop = g_main_loop_new (NULL, TRUE);
+
+  curdir = g_file_new_for_path (".");
+  monitor = g_file_monitor_directory (curdir, 0, NULL, &error);
+  if (!monitor)
+    exit (1);
+  g_signal_connect (monitor, "changed",
+                   G_CALLBACK (on_dir_changed), &data);
+
+  new_argv = g_ptr_array_new ();
+  for (i = 1; i < argc; i++)
+    g_ptr_array_add (new_argv, argv[i]);
+  g_ptr_array_add (new_argv, NULL);
+
+  if (!g_spawn_async (NULL, (char**)new_argv->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                      NULL, NULL, &pid, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      return 1;
+    }
+  g_child_watch_add (pid, on_child_exited, &data);
+
+  g_main_loop_run (data.loop);
+
+  if (!data.exited)
+    kill (data.pid, SIGTERM);
+
+  return 0;
+}